﻿using S7.Net;
using S7.Net.Types;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;

namespace 西门子PLC数据采集
{
    public class S7PLC
    {
        /// <summary>
        /// PLC 对象
        /// </summary>
        Plc? plc;
        /// <summary>
        /// PLC连接状态
        /// </summary>
        public bool IsConnected = false;
        string ip;
        short rack;
        short slot;

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="cpuType"></param>
        /// <param name="ip"></param>
        /// <param name="rack"></param>
        /// <param name="slot"></param>
        public S7PLC(CpuType cpuType, string ip, short rack, short slot)
        {
            this.ip = ip;
            this.rack = rack;
            this.slot = slot;

            plc = new Plc(cpuType, ip, rack, slot);
        }

        /// <summary>
        /// 操作委托
        /// </summary>
        /// <param name="status"></param>
        public delegate void OperationEventDelegate(string log);

        /// <summary>
        /// 操作事件
        /// </summary>
        /// <param name="status"></param>
        public event OperationEventDelegate? OperationEvent;

        /// <summary>
        /// 操作日志
        /// </summary>
        /// <param name="log"></param>
        public void OperationLog(string log,string addr = "")
        {
            if (log.Contains('\t'))
            {
                log = log.Replace("\t","    ");
            }
            if (OperationEvent is not null)
            {
                if (addr.Length > 1)
                {
                    OperationEvent($"[{System.DateTime.Now.ToString("F")}]    {addr}    {log}");
                }
                else
                {
                    OperationEvent($"[{System.DateTime.Now.ToString("F")}]    {ip}    {log}");
                }   
            }
        }

        /// <summary>
        /// 连接
        /// </summary>
        public void Connect()
        {
            if (plc is not null && IsIpEffective(ip))
            {
                OperationLog($"IP地址：{ip} 开始连接");
                try
                {
                    if (plc.IsConnected == false)
                    {
                        plc.Open();

                        IsConnected = true;

                        OperationLog($"IP地址：{ip} 连接成功");
                    }
                }
                catch (Exception ex)
                {
                    OperationLog($"plc.Open()-->{ex.Message}");
                }
            }
            else
            {
                OperationLog($"IP地址：{ip} 连接结束");
            }
        }

        /// <summary>
        /// 关闭
        /// </summary>
        public void Close()
        {
            if (plc is not null)
            {
                plc.Close();

                IsConnected = false;

                plc = null;

                OperationLog($"IP地址：{ip} 连接断开");
            }
        }

        /// <summary>
        /// 单次读取数据
        /// </summary>
        /// <param name="addr"></param>
        /// <returns></returns>
        public object Read(DataType dataType, string addr)
        {
            if (plc is not null && IsConnected)
            {
                try
                {
                    object value = DataConversion(dataType, plc.Read(addr.ToUpper())!);

                    OperationLog($"读取值：{value}", addr);

                    return value;    
                }
                catch (Exception ex)
                {
                    OperationLog($"数据读取错误,错误代码：”-0.001-->{ex.Message}", addr);         
                }
            }
            return "-0.001";
        }

        /// <summary>
        /// 数据转换
        /// </summary>
        /// <param name="serialNumber"></param>
        /// <param name="Value"></param>
        /// <returns></returns>
        public object DataConversion(DataType dataType, object value)
        {
            object? OutValue = null;

            try
            {
                switch (dataType)
                {
                    //Bool
                    case DataType.BOOL:
                        OutValue = ((bool)value);
                        break;
                    //Byte
                    case DataType.BYTE:
                        OutValue = ((byte)value);
                        break;
                    //Word
                    //Int
                    case DataType.WORD:
                    case DataType.INT:
                        OutValue = ((ushort)value).ConvertToShort();
                        break;
                    //DWord
                    //DInt
                    case DataType.DINT:
                    case DataType.DWORD:
                        OutValue = ((uint)value).ConvertToInt();
                        break;
                    //Real
                    case DataType.REAL:
                        OutValue = ((uint)value).ConvertToFloat();
                        break;
                    default:
                        break;
                }  
            }
            catch (Exception ex)
            {
                OperationLog($"plc.Read()-->{ex.Message}");
            }

            return OutValue!;
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <param name="serialNumber"></param>
        /// <param name="value"></param>
        /// <param name="addr"></param>
        public void Write(DataType dataType, string addr, string value)
        {
            if (plc is not null && IsConnected)
            {
                switch (dataType)
                {
                    //Bit
                    case DataType.BOOL:
                        plc.Write(addr.ToUpper(), Convert.ToBoolean(value));
                        break;
                    //Byte
                    case DataType.BYTE:
                        plc.Write(addr.ToUpper(), Convert.ToByte(value));
                        break;
                    //Word Int
                    case DataType.WORD:
                    case DataType.INT:
                        plc.Write(addr.ToUpper(), ushort.Parse(value));
                        break;
                    //DWord Dint
                    case DataType.DINT:
                    case DataType.DWORD:
                        plc.Write(addr.ToUpper(), Int32.Parse(value));
                        break;
                    //Real
                    case DataType.REAL:
                        plc.Write(addr.ToUpper(), float.Parse(value));
                        break;
                    default:
                        break;
                }
                try
                {
                    OperationLog($"写入值：{value}\t回读值：{DataConversion(dataType, plc.Read(addr.ToUpper())!)}", addr);
                }
                catch (Exception ex)
                {
                    OperationLog($"{ex.Message}", addr);
                }     
            }
        }

        /// <summary>
        /// 批量读取列表
        /// </summary>
        private List<DataItem>? dataItems;

        public List<DataItem>? DataItems { get => dataItems; set => dataItems = value; }

        /// <summary>
        /// 批量读取
        /// </summary>
        public void ReadMultiple()
        {
            //DataItem dataItem = new();
            //dataItem.DataType = DataType.DataBlock;
            //dataItem.DB = 1;
            //dataItem.VarType = VarType.Word;
            //dataItem.StartByteAdr = 4;

            if (plc is not null && dataItems is not null)
            {
                if (plc.IsConnected)
                {
                    plc.ReadMultipleVars(dataItems);
                }
                else
                {
                    OperationLog($"plc 未连接");
                }
            }
        }

        /// <summary>
        /// ip 检查
        /// </summary>
        /// <param name="strIP"></param>
        /// <returns></returns>
        public bool IsIpEffective(string ipAddress)
        {
            if (IsValidIpAddress(ipAddress))
            {
                OperationLog($"IP地址：{ipAddress} 有效地址");
            }
            else
            {
                OperationLog($"IP地址：{ipAddress} 无效地址");
                return false;
            }

            Ping pingSender = new Ping();
            try
            {
                PingReply reply = pingSender.Send(ipAddress);

                if (reply != null && reply.Status == IPStatus.Success) 
                { 
                    OperationLog($"IP地址：{ipAddress} 连接可达");
                    return true;
                }
                else
                {
                    OperationLog($"IP地址：{ipAddress} 无法访问");
                    return false;     
                }
            }
            catch (Exception ex)
            {
                OperationLog($"IP地址：（检测出现错误）{ex.Message}");
                return false;
            }
        }

        public static bool IsValidIpAddress(string ipAddress)
        {
            Regex rx = new Regex(@"\b((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))\b");
            if (rx.IsMatch(ipAddress) == false)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}
